﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using TheTVDB.Properties;
using System.IO;
using System.Net;

namespace TheTVDB
{
    public class TVDB
    {
        public string APIKey { get; private set; }
        public List<Mirror> XmlMirrors { get; private set; }
        public List<Mirror> BannerMirrors { get; private set; }
        public List<Mirror> ZipMirrors { get; private set; }
        public List<Language> Languages { get; private set; }
        public Language CurrentLanguage { get; set; }

        private const string DefaultMirrorUrl = "http://www.thetvdb.com/api/{0}/mirrors.xml";
        private const string ServerTimeUrl = "/api/{0}/updates/updates_day.zip";
        private int currentXmlMirror, currentBannerMirror, currentZipMirror;
        private Random mirrorRandomizer = new Random();
        public TVDB(string apiKey)
        {
            APIKey = apiKey;
            XmlMirrors = new List<Mirror>();
            BannerMirrors = new List<Mirror>();
            ZipMirrors = new List<Mirror>();
            Languages = new List<Language>();
        }

        public IList<Series> Search(string showName)
        {
            XmlDocument results = DownloadXml("/api/GetSeries.php?seriesname=" + Uri.EscapeUriString(showName));
            List<Series> shows = new List<Series>();

            if (results != null)
            {
                foreach (XmlElement s in results.SelectNodes("//Series"))
                {
                    shows.Add(Series.FromXml(s));
                }
            }
            return shows;
        }

        public Series GetSeries(int id)
        {
            Dictionary<string, XmlDocument> doc = FetchXmlZip("/api/"+APIKey+"/series/"+id+"/all/"+CurrentLanguage.Abbreviation+".zip");
            Series s = Series.FromXmlDocument(doc[CurrentLanguage.Abbreviation]);
            if (doc.ContainsKey("banners"))
            {
                foreach (XmlElement e in doc["banners"].SelectNodes("//Banner"))
                {
                    s.Banners.Add(Banner.FromXml(e));
                }
            }
            return s;
        }

        private Dictionary<string, XmlDocument> FetchXmlZip(string urlWithoutMirror)
        {
            Dictionary<string, XmlDocument> docs = new Dictionary<string, XmlDocument>();

            byte[] data;

            if(!TryDownloadZip(urlWithoutMirror, out data))
            {
                return null;
            }

            using (MemoryStream ms = new MemoryStream(data))
            {
                using (ICSharpCode.SharpZipLib.Zip.ZipFile zf = new ICSharpCode.SharpZipLib.Zip.ZipFile(ms))
                {

                    foreach (ICSharpCode.SharpZipLib.Zip.ZipEntry f in zf)
                    {
                        using (StreamReader reader = new StreamReader(zf.GetInputStream(f)))
                        {
                            string s = reader.ReadToEnd();
                            XmlDocument doc = new XmlDocument();
                            doc.LoadXml(s);
                            docs.Add(Path.GetFileNameWithoutExtension(f.Name), doc);
                        }
                    }
                }
            }
            return docs;
        }

        public string GetBannerUrl(string bannerPath)
        {
            return BannerMirrors[currentBannerMirror].Path + "/banners/" + bannerPath;
        }

        public Stream GetBannerStream(string bannerPath)
        {
            byte[] b;
            if (TryDownloadBanner(bannerPath, out b))
            {
                return new MemoryStream(b);
            }
            throw new ArgumentException("Unable to download banner!");
        }

        public bool TryDownloadBanner(string bannerPath, out byte[] data)
        {
            int mirrorTryCount = 0;
            using (WebClient c = new WebClient())
            {
                while (mirrorTryCount < BannerMirrors.Count)
                {
                    try
                    {
                        data =  c.DownloadData(BannerMirrors[currentBannerMirror].Path + "/banners/" + bannerPath);
                        return true;
                    }
                    catch
                    {
                        mirrorTryCount++;
                        currentBannerMirror = (++currentBannerMirror) % BannerMirrors.Count;

                    }
                }
            }
            data = new byte[0];
            return false;
        }

        private bool TryDownloadZip(string urlWithoutMirror, out byte[] data)
        {
            int mirrorTryCount = 0;
            using (WebClient c = new WebClient())
            {
                while (mirrorTryCount < ZipMirrors.Count)
                {
                    try
                    {
                        data = c.DownloadData(ZipMirrors[currentZipMirror].Path + urlWithoutMirror);
                        return true;
                    }
                    catch
                    {
                        mirrorTryCount++;
                        currentZipMirror = (++currentZipMirror) % ZipMirrors.Count;
                    }
                }
            }
            data = new byte[0];
            return false;
        }

        private XmlDocument DownloadXml(string urlWithoutMirror)
        {

            int mirrorTryCount = 0;
            XmlDocument doc = new XmlDocument();
            while (mirrorTryCount < ZipMirrors.Count)
            {
                try
                {
                    doc.Load(XmlMirrors[currentXmlMirror].Path + urlWithoutMirror);
                    return doc;
                }
                catch
                {
                    mirrorTryCount++;
                    currentXmlMirror = (++currentXmlMirror) % XmlMirrors.Count;
                }
            }
            return null;
        }





        #region Languages
        public void LoadDefaultLanguages()
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(Resources.languages);
            LoadLanguages(doc);
        }

        public void LoadLanguages(XmlDocument languagesXml)
        {
            foreach (XmlElement lang in languagesXml.SelectNodes("//Language"))
            {
                Language l = Language.FromXml(lang);
                if (l.Abbreviation == "en")
                    CurrentLanguage = l;
                Languages.Add(l);
            }
        }
        #endregion
        #region Mirrors
        public void AddDefaultMirrors()
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(string.Format(DefaultMirrorUrl, APIKey));
            AddMirrors(doc);
            currentBannerMirror = mirrorRandomizer.Next(BannerMirrors.Count);
            currentXmlMirror = mirrorRandomizer.Next(XmlMirrors.Count);
            currentZipMirror = mirrorRandomizer.Next(ZipMirrors.Count);
        }

        public void AddMirrors(XmlDocument mirrorsXml)
        {
            XmlNodeList list = mirrorsXml.SelectNodes("//Mirror");
            foreach (XmlNode node in list)
            {
                Mirror m = Mirror.FromXml(node);
                if ((m.Type & MirrorType.Banner) == MirrorType.Banner)
                    BannerMirrors.Add(m);
                if ((m.Type & MirrorType.Zip) == MirrorType.Zip)
                    ZipMirrors.Add(m);
                if ((m.Type & MirrorType.Xml) == MirrorType.Xml)
                    XmlMirrors.Add(m);
            }
        }
        #endregion

        public DateTime GetServerTime()
        {
            Dictionary<string,XmlDocument> docs = FetchXmlZip(string.Format(ServerTimeUrl, APIKey));



            XmlElement n = (XmlElement)docs["updates_day"].SelectSingleNode("//Data");
            return ConvertFromUnixTimestamp(Int64.Parse(n.GetAttribute("time")));
        }
        static DateTime ConvertFromUnixTimestamp(long timestamp)
        {
            DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return origin.AddSeconds(timestamp);
        }

    }
}
